package com.lx.boot.job;

import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.lx.boot.OS;
import com.lx.boot.email.Mail;
import com.lx.boot.es.EsInfo;
import com.lx.boot.lock.LockManager;
import com.lx.boot.lock.RedisLock;
import com.lx.boot.web.annotation.Auth;
import com.lx.boot.web.annotation.Auth.BoolOption;
import com.lx.boot.web.filter.SysApiEncryptFilter;
import com.lx.constant.DefaultBaseConstant;
import com.lx.entity.Result;
import com.lx.util.LX;
import io.swagger.v3.oas.annotations.Hidden;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.logging.LogLevel;
import org.springframework.boot.logging.LoggingSystem;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.SchedulingConfigurer;
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
import org.springframework.scheduling.support.CronTrigger;
import org.springframework.scheduling.support.SimpleTriggerContext;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.Executor;
import java.util.concurrent.ScheduledThreadPoolExecutor;


@Slf4j
@Hidden
@Configuration
@EnableScheduling
@RestController
@MapperScan("com.lx.boot.job")
@ConditionalOnProperty(name = DefaultBaseConstant.LXJOB_ENABLE, havingValue = "true", matchIfMissing = false)
@Auth(value = false,apiFilter = SysApiEncryptFilter.class, csrf = BoolOption.FALSE, firmIdRequired = BoolOption.FALSE)
public class LXJob implements SchedulingConfigurer , CommandLineRunner {
    private ScheduledTaskRegistrar scheduledTaskRegistrar;
    private ScheduledThreadPoolExecutor scheduledThreadPoolExecutor;
    public static Set<String> scheduledChannel = new HashSet<>();// 记录哪些配置已经生成定时任务了,避免重复生成定时任务

    @Autowired
    private JobTaskDao jobTaskDao;

    @Autowired
    private Executor executor;

    //说明:
    /** @author ylx 2022/9/9 14:59:02 */
    @GetMapping("/lxjob/queryJobTaskListPage")
    @Schema(description = "查询定时任务列表")
    public Result<List<JobTask>> queryJobTaskListPage(){
        return Result.ok(jobTaskDao.selectList(new QueryWrapper<>()));
    }
    @GetMapping("/lxjob/queryJobTaskExecList")
    @Schema(description = "查询定时任务前10次执行列表")
    public Result<List<String>> queryJobTaskExecList(String cron){
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        CronTrigger trigger = new CronTrigger(cron);
        SimpleTriggerContext triggerContext = new SimpleTriggerContext();
        List<String> ls = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            Date date = trigger.nextExecutionTime(triggerContext);
            System.out.println(simpleDateFormat.format(date));
            ls.add(simpleDateFormat.format(date));
            triggerContext.update(date,date,date);
        }
        return Result.ok(ls);
    }

    //说明:
    /** @author ylx 2022/9/9 14:59:02 */
    @PostMapping("/lxjob/insertJobTask")
    @Schema(description = "新增定时任务")
    public Result insertJobTask(@RequestBody JobTask dto){
        dto.setLjtId(LX.getDate("yyMMddHHmmss"));
        jobTaskDao.insert(dto);
        addScheduledTask(new LXTask(dto));
        return Result.ok();
    }

    //说明:
    /** @author ylx 2022/9/9 14:59:02 */
    @PostMapping("/lxjob/updateJobTask")
    @Schema(description = "更新定时任务")
    public Result updateJobTask(@RequestBody JobTask dto){
        deleteSchedule(dto.getLjtId());
        jobTaskDao.updateById(dto);
        if ("1".equals(dto.getIsOpen()) && "1".equals(dto.getIsEnabled())){
            addScheduledTask(new LXTask(dto));
        }
        return Result.ok();
    }

    @PostMapping("/lxjob/execJob")
    @Schema(description = "执行一次定时任务")
    public Result execJob(@RequestBody JobTask dto){
        executor.execute(new LXTask(dto));
        return Result.ok();
    }

    @Override
    public void run(String... args) throws Exception {
        OS.getBean(LoggingSystem.class).setLogLevel("com.lx.boot.job", LogLevel.DEBUG);
        List<JobTask> tasks = jobTaskDao.selectList(new LambdaQueryWrapper<JobTask>().eq(JobTask::getIsOpen,"1"));
        if (LX.isNotEmpty(tasks)){
            tasks.forEach(task->{
                addScheduledTask(new LXTask(task));
            });
        }
    }

    // 获取定时任务配置
    @Override
    public void configureTasks(ScheduledTaskRegistrar scheduledTaskRegistrar) {
        this.scheduledTaskRegistrar = scheduledTaskRegistrar;
        this.scheduledTaskRegistrar.setScheduler(setTaskExecutors());
    }

    // 定时任务的线程池大小配置
    @Bean()
    public Executor setTaskExecutors(){
        scheduledThreadPoolExecutor = new ScheduledThreadPoolExecutor(1);
        return scheduledThreadPoolExecutor;
    }

     // 新增定时任务 LXTask implements Runnable,每个task中有一个ljtId唯一值
    public synchronized void addScheduledTask(LXTask lxTask) {
    	// 每个定时任务有一个唯一值用来标识该定时任务
        if(!scheduledChannel .contains(lxTask.getTask().getLjtId())){
            scheduledThreadPoolExecutor.setCorePoolSize(scheduledThreadPoolExecutor.getCorePoolSize()+1);
            // 避免原有的定时任务再次被添加到任务中重复执行
            scheduledTaskRegistrar.setTriggerTasks(new HashMap<>());
            scheduledTaskRegistrar.setCronTasks(new HashMap<>());
            scheduledTaskRegistrar.setFixedRateTasks(new HashMap<>());
            scheduledTaskRegistrar.setFixedDelayTasks(new HashMap<>());
            scheduledTaskRegistrar.addCronTask(lxTask, lxTask.getTask().getTaskCycle());
            // 当执行了afterPropertiesSet后上面的addCronTask才会生效
            scheduledTaskRegistrar.afterPropertiesSet();
			// 将其加入到缓存中标识该定时任务已经生成
            scheduledChannel.add(lxTask.getTask().getLjtId());
            log.info("定时任务{}启动成功!",lxTask.getTask());
        }
    }

   // 删除定时任务
    public synchronized void deleteSchedule(String ljtId) {
        if(scheduledChannel.contains(ljtId)) {
            scheduledTaskRegistrar.getScheduledTasks().forEach(v -> {
                if (v.getTask().getRunnable() instanceof LXTask) {
                    LXTask t = (LXTask) v.getTask().getRunnable();
                    if (t.getTask().getLjtId().equals(ljtId)) {
                        v.cancel();
                        scheduledChannel.remove(ljtId);
                        scheduledThreadPoolExecutor.setCorePoolSize(scheduledThreadPoolExecutor.getCorePoolSize()-1);
                        log.info("定时任务{}停止成功!",ljtId);
                        return;
                    }
                }
            });
        }
    }


    @Getter
    public class LXTask implements Runnable{
        private JobTask task;
        public LXTask(JobTask task) {
            this.task = task;
        }
        @Override
        public void run() {
            OS.setLogTraceId(task.getLjtId()+LX.uuid32(3));
            try {
                log.info("开始执行任务" + task);
                Object bean = OS.getBean(LX.initialLower(task.getClassName()));
                bean.getClass().getDeclaredMethod(task.getMethodName()).invoke(bean);
                log.info("结束执行任务"+task);
            } catch (Exception e) {
                log.error("执行定时任务出错!",e);
                if (LX.isNotEmpty(OS.getProperty("email.sendMember")) && LX.isNotEmpty(task.getEmail())){
                    EsInfo esInfo = OS.getBeanProperty(EsInfo.class);
                    Mail.send(LX.LOCALHOST_IP+" 任务执行失败!==>"+task.getLjtId()+" : "+task.getTaskName() ,e.getMessage()+"\n"+esInfo.getHosts()+" : " +OS.getLogTraceId(), task.getEmail().split(","));
                }
            }
        }
    }
}
